/*
 * CITS1002 Project 1 2012
 * Name:             Guilherme R. Lampert
 * Student number:   21203005
 * Date:             21/09/20012
 *
 * Compiled with:
 * gcc -ansi -Wall -Werror -pedantic -pedantic-errors myafl.c -o myafl
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

/* ------------------------------------------------------------------------ */

struct Match {

	/* 'team[N]' will hold an index to the 'aflTeams[N]' array */
	int team[2];
	int teamGoals[2], teamBehinds[2];

	/* Next match in the list or NULL */
	struct Match * next;
};

struct Round {

	int numOfMatches;
	struct Match * matches;
};

#define MAX_AFL_ROUNDS 23
static struct Round aflRounds[MAX_AFL_ROUNDS];
static int numOfAflRounds;

#define TEAM_NAME_MAX_LEN 30
struct Team {

	/* 'name' is read from the 'teamnames' file */
	char name[TEAM_NAME_MAX_LEN + 1];
	/* This statistics will be computed latter on */
	int numGamesPlayed, numGamesWon, numGamesLost, numGamesDraw;
	int behindsScored, behindsTaken, premiershipPoints;
	float teamsPercentage;
};

#define MAX_AFL_TEAMS 24
static struct Team aflTeams[MAX_AFL_TEAMS];
static int numOfAflTeams;

/* ------------------------------------------------------------------------ */

static void Cleanup(void) {

	int i;
	struct Match * match, * save;

	/* Free the dynamically allocated links of the list, if any */
	for (i = 0; i < numOfAflRounds; ++i) {

		match = aflRounds[i].matches;
		while (match) {

			save = match->next;
			free(match);
			match = save;
		}
	}
}

static void Error(const char * format, ...) {

	va_list args;

	va_start(args, format);
	vfprintf(stderr, format, args);
	va_end(args);

	exit(EXIT_FAILURE);
}

static int ValidateTeamName(const char * input) {

	int i;
	for (i = 0; i < numOfAflTeams; ++i) {

		if (strcmp(input, aflTeams[i].name) == 0) {

			return (i);
		}
	}

	Error("Found a team name in the results file"
	      "that was not in the team names file!\n");

	/* Just to keep the compiler happy.
	Error() doesn't return */
	return (-1);
}

static int ParseTeamGoals(const char * input) {

	int i;
	if (sscanf(input, "%i", &i) != 1) {
		Error("Results file was invalid! Bad number of goals...\n");
	}
	return (i);
}

static int ParseTeamBehinds(const char * input) {

	int i;
	if (sscanf(input, "%i", &i) != 1) {
		Error("Results file was invalid! Bad number of behinds...\n");
	}
	return (i);
}

static void ParseMatchResult(const char * lineBuf, int nextIndex) {

/* This macro will be handy to read a field
 * and advance our line 'pointer'. It skips any
 * number of blank characters between fields.
 */
#define READ_FIELD() \
b = tempBuf; \
for (;;) { \
	if ((*l == '\0') || isspace(*l)) { \
		*b = '\0'; \
		while (isspace(*l) && (*l != '\0')) { ++l; } \
		break; \
	} \
	*b++ = *l++; \
}

	int i;
	char * b;
	const char * l;
	struct Match * match, * temp;
	char tempBuf[1024];

	match = (struct Match *)calloc(1, sizeof(struct Match));
	if (!match) {

		Error("Out of memory !\n");
	}
	l = lineBuf;

	for (i = 0; i < 2; ++i) {

		/* Read team[i] name */
		READ_FIELD();
		match->team[i] = ValidateTeamName(tempBuf);

		/* Read team[i] goals */
		READ_FIELD();
		match->teamGoals[i] = ParseTeamGoals(tempBuf);

		/* Read team[i] behinds */
		READ_FIELD();
		match->teamBehinds[i] = ParseTeamBehinds(tempBuf);
	}

	/* Add to list */
	temp = aflRounds[nextIndex].matches;
	aflRounds[nextIndex].matches = match;
	aflRounds[nextIndex].matches->next = temp;

	aflRounds[nextIndex].numOfMatches++;

#undef READ_FIELD
}

static void ReadRoundsResults(const char * fileName) {

	FILE * file;
	char lineBuf[1024];

	file = fopen(fileName, "rt");

	if (!file) {

		Error("Unable to open results file: %s\n", fileName);
	}

	while (fgets(lineBuf, sizeof(lineBuf), file) && (numOfAflRounds < MAX_AFL_ROUNDS)) {

		if (*lineBuf == '\n') {

			/* A blank line indicates a new round */
			++numOfAflRounds;

		} else {

			/* Try to parse and add a match result */
			ParseMatchResult(lineBuf, numOfAflRounds);
		}
	}

	fclose(file);

	if (numOfAflRounds == 0) {

		Error("No rounds results were read from the file: %s!\n"
		      "Make sure the file is in the expected format...\n", fileName);

	} else if (numOfAflRounds == MAX_AFL_TEAMS) {

		printf("Warning! Too many rounds results found in the file. "
		       "Max supported is %i\n", MAX_AFL_ROUNDS);
	}
}

static void ReadTeamNames(const char * fileName) {

	FILE * file;
	char lineBuf[TEAM_NAME_MAX_LEN + 1];

	file = fopen(fileName, "rt");

	if (!file) {

		Error("Unable to open team names file: %s\n", fileName);
	}

	while (fgets(lineBuf, sizeof(lineBuf), file) && (numOfAflTeams < MAX_AFL_TEAMS)) {

		if (sscanf(lineBuf, "%s", aflTeams[numOfAflTeams].name) == 1) {

			/* Team name was read successfully */
			++numOfAflTeams;
		}
	}

	fclose(file);

	if (numOfAflTeams == 0) {

		Error("No team names were read from the file: %s!\n"
		      "Make sure the file is in the expected format...\n", fileName);

	} else if (numOfAflTeams == MAX_AFL_TEAMS) {

		printf("Warning! Too many team names found in the file. "
		       "Max supported is %i\n", MAX_AFL_TEAMS);
	}
}

static void ComputeStatistics(void) {

	struct Match * match;
	int i, j, otherTeamsScore, myScore, which;

	/* For ALL teams... */
	for (i = 0; i < numOfAflTeams; ++i) {

		/* For ALL game rounds... */
		for (j = 0; j < numOfAflRounds; ++j) {

			/* For ALL matches... (list iteration) */
			match = aflRounds[j].matches;
			while (match) {

				if (i == match->team[0]) {
					which = 0;
				} else if (i == match->team[1]) {
					which = 1;
				} else {
					/* This team wasn't in this match */
					which = -1;
				}

				if (which >= 0) { /* Compute stats: */

					aflTeams[i].numGamesPlayed++;

					/* Each goal = 6 behinds */
					myScore = match->teamBehinds[which] + (match->teamGoals[which] * 6);
					aflTeams[i].behindsScored += myScore;

					otherTeamsScore = match->teamBehinds[!which] + (match->teamGoals[!which] * 6);
					aflTeams[i].behindsTaken += otherTeamsScore;

					/* Find out if they won, lost or draw */
					if (myScore > otherTeamsScore) {
						aflTeams[i].numGamesWon++;
						aflTeams[i].premiershipPoints += 4; /* Each victory gives 4 premiership points */
					} else if (myScore < otherTeamsScore) {
						aflTeams[i].numGamesLost++;
					} else {
						/* In the event of a draw game, both teams get 2 points each */
						aflTeams[match->team[0]].premiershipPoints += 2;
						aflTeams[match->team[1]].premiershipPoints += 2;
						aflTeams[i].numGamesDraw++;
					}
				}

				match = match->next;
			}
		}

		/* teamsPercentage - the ratio of the total number of behinds scored by that team,
		 * to the total number of behinds scored by all teams playing in games against them.
		 */
		aflTeams[i].teamsPercentage = ((float)aflTeams[i].behindsScored / (float)aflTeams[i].behindsTaken) * 100.0f;
	}
}

static void OutputFinalLadder(const char * fileName) {

	int i;
	FILE * file;

	/* Open file for writing text 'wt */
	file = fopen(fileName, "wt");

	if (!file) {

		Error("Unable to write file: %s\n", fileName);
	}

	/* Sample output of a line:
	 * Alligators 12 5 7 0 1045 1192 87.67 20
	 * Consisting of:
	 * <teamName> <numGamesPlayed> <numGamesWon> <numGamesLost> <numGamesDraw>
	 * <behindsScored> <behindsTaken> <teamsPercentage (2 decimal digs)> <premiershipPoints>
	 * All the above in 1 line.
	 */

	/* For ALL teams... print the statistics */
	for (i = 0; i < numOfAflTeams; ++i) {

		fprintf(file, "%s %i %i %i %i %i %i %.2f %i\n",
				aflTeams[i].name, aflTeams[i].numGamesPlayed, aflTeams[i].numGamesWon,
				aflTeams[i].numGamesLost, aflTeams[i].numGamesDraw, aflTeams[i].behindsScored,
				aflTeams[i].behindsTaken, aflTeams[i].teamsPercentage, aflTeams[i].premiershipPoints);
	}

	fclose(file);
}

static void SortLadderFile(const char * fileName) {

	pid_t pid;
	char * cmdArgs[5];

	pid = fork();

	if (pid < 0) {

		Error("fork() failed! %s.\nOutput not sorted!\n", strerror(errno));

	} else if (pid == 0) {
		/* Child process */

		/* Redirect stdout to the requested file */
		if (!freopen(fileName, "r+", stdout)) {

			Error("Failed to redirect standard output! %s\n", strerror(errno));
		}

		/* $ sort -k9nr,9 -k8nr,8 fileName */
		cmdArgs[0] = "/usr/bin/sort";
		cmdArgs[1] = "-k9nr,9";
		cmdArgs[2] = "-k8nr,8";
		cmdArgs[3] = (char *)fileName;
		cmdArgs[4] = (char *)0;

		if (execv("/usr/bin/sort", cmdArgs) < 0) {

			Error("execv() failed! %s.\nOutput not sorted!\n", strerror(errno));
		}

		fclose(stdout);
	}
}

int main(int argc, char ** argv) {

	/* Command args validation */
	if (argc < 4) {

		/* Must provide the 3 files (first arg is the executable name 'myafl' */
		Error("Not enough arguments! Usage: $ myafl teamnames results ladder\n");
	}

	/* Register a cleanup routine to reclaim resources back if we call exit() */
	atexit(Cleanup);

	/* First arg should be the 'teamnames' file */
	ReadTeamNames(argv[1]);

	/* Second arg should be the 'results' file with the outcome of the AFL rounds */
	ReadRoundsResults(argv[2]);

	/* Compute stats based on the input */
	ComputeStatistics();

	/* Write intermediate output to the file */
	OutputFinalLadder(argv[3]);

	/* Calls the Unix utility 'sort' on the file contents
	and write results back into the same file (3rd arg) */
	SortLadderFile(argv[3]);

	return (EXIT_SUCCESS);
}
